Skip to content

android: support text and link sharing - #814

Merged
willh-ts merged 1 commit into
mainfrom
will/taildrop/links
Sep 2, 2026
Merged

android: support text and link sharing#814
willh-ts merged 1 commit into
mainfrom
will/taildrop/links

Conversation

@willh-ts

@willh-ts willh-ts commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

This enhances the Android share feature as follows:

  • Text and URLs will be recognized and shared via the .txt and .url files
  • Files, text, and URLs will now show a small banner atop the devices list, mirroring iOS behavior.
  • If multiple taildrops arrive, tapping the banner will open a bottom sheet to take action on them individually.
  • Files can be opened or the enclosing taildrop folder opened.
  • Text can be copied.
  • URLs can be opened in the default browser.
  • "Recently Used" taildrop targets will be locally cached and presented atop the list. Presentation & max length matches iOS behavior.
  • Simple polling for online status is added to avoid the need to reload the share sheet to find devices that connect while it is visible.
  • Fixes a bug that can cause the share sheet to be non-functional if you share; don't exit the view; share again

The iOS and macOS share extensions are currently receiving minor
Taildrop enhancements to support sharing of Text and URLs from
the OS-level share menu. To allow older clients to seamlessly receive
shares from newer clients, the .txt and .url suffixes were chosen.

iOS & macOS (in https://github.com/tailscale/corp/pull/44001), Android
(with these changes), and soon Windows and Linux will recognize these
file types and respond accordingly upon receipt of them:

  • .txt: Notification presented to "copy" the text to the clipboard.
  • .url: Notification presented to "open" the link

For platforms that lack in-app notifications, the files will additionally
be saved to the Taildrop folder as "Text [timestamp].txt",
"URL [timestamp].inetloc", or "URL [timestamp].url". This addresses
a limitation for users who explicitly opt-out of app notifications. Those
users would otherwise receive no transferred content whatsoever.

Upon use or dismissal of URL shares, the .url file will be deleted. This
is done to avoid piling up generally unusable .url files on Android in the
Taildrop folder.

All other taildrop file transfer functionality remain unchanged.

updates tailscale/tailscale#4896
updates tailscale/tailscale#4996
fixes tailscale/tailscale#16850

@willh-ts
willh-ts requested review from barnstar and kari-ts June 26, 2026 20:09
@willh-ts
willh-ts force-pushed the will/taildrop/links branch from 2d7aa5b to cbda6b1 Compare June 26, 2026 21:09
Comment thread android/src/main/java/com/tailscale/ipn/ui/viewModel/MainViewModel.kt Outdated
Comment thread android/src/main/java/com/tailscale/ipn/ShareActivity.kt Outdated
Comment thread android/src/main/java/com/tailscale/ipn/ui/view/TaildropBannerView.kt Outdated
object BrowserOpener {
// Pin the ACTION_VIEW to the user's default browser package so non-browser
// apps that also claim http(s) (e.g. the Google app) don't trigger the chooser.
fun openInDefaultBrowser(context: Context, uri: Uri): Boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we restrict to http/https uris?

@willh-ts willh-ts Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated slightly to let this handle both http/https and any supported protocol:

Tested, functional

  • http:, https:, tel:, sms:, mailto:.

Tested, not yet supported, falls through to "copy" handler on tap:

  • tailscale:

Tested, fails & falls through to "copy" handler:

  • invalid:

The latter 2 could be improved slightly (i.e., we could show "copy" instead of "open" for the subtitle) if we used PackageManager.resolveActivity to preemptively check whether a URL can be handled, but as I understand it, older Android versions and some OEM flavors restrict access to that.

Screenshot_20260629_120306

Comment thread android/src/main/java/com/tailscale/ipn/util/TaildropUsageTracker.kt Outdated
@willh-ts
willh-ts force-pushed the will/taildrop/links branch 3 times, most recently from 5598b66 to f0f55e3 Compare June 29, 2026 16:33
@willh-ts
willh-ts marked this pull request as draft June 30, 2026 15:13
@willh-ts
willh-ts force-pushed the will/taildrop/links branch 2 times, most recently from f894b47 to 57adc08 Compare July 2, 2026 19:33
@willh-ts
willh-ts marked this pull request as ready for review July 2, 2026 20:45
@willh-ts
willh-ts requested a review from kari-ts July 2, 2026 20:46
Intent(context, InlineShareActionReceiver::class.java).apply {
action = InlineShareActionReceiver.ACTION_CONSUME
putExtra(InlineShareActionReceiver.EXTRA_KIND, pending.kind.name.lowercase())
putExtra(InlineShareActionReceiver.EXTRA_CONTENT, pending.content)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if this hits the size limit? https://developer.android.com/guide/components/activities/parcelables-and-bundles#sdbp looks like this is 1MB

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I wasn't familiar with that. Updated to send and ref by ID.

Comment thread android/src/main/java/com/tailscale/ipn/util/ShareFileHelper.kt Outdated
Comment thread android/src/main/java/com/tailscale/ipn/ui/notifier/Notifier.kt Outdated
Comment thread android/src/main/java/com/tailscale/ipn/ShareActivity.kt Outdated
@willh-ts
willh-ts force-pushed the will/taildrop/links branch 2 times, most recently from 6f3fa7c to 31569a6 Compare August 21, 2026 21:05
@willh-ts
willh-ts requested a review from kari-ts August 21, 2026 21:16

@kari-ts kari-ts left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for bearing with me!

InlineShare.Kind.URL -> openUrl(context, item.share.content)
InlineShare.Kind.TEXT -> copyToClipboard(context, item.share.content)
}
Notifier.removeInlineShare(item.share.id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like copyToClipboard returns if there is a failure. should we still call removeInlineShare in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make copyToClipboard return Boolean and gate removeInlineShare +
TaildropNotifier.cancel on it. Same for openUrl, since it falls back to the clipboard.
Also wrapping setPrimaryClip in runCatching. It can throw out of consume().


private fun encryptedPrefs() = (UninitializedApp.get() as App).getEncryptedPrefs()

fun save(inbox: List<PendingInlineShare>) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a size bound on inbox before persisting? I wonder if a large taildrop or a bunch of pending saves might make this synchronous save problematic (cause lags, performance issues)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bound today, afaik, and thinking about why led somewhere bigger.

I'd been assuming this path only sees modest text selections and URLs, since that's what the share sheet produces. But routing is by filename, not origin, so anything named md5.txt lands here. Someone taildropping a hash-named log dump would hit this path and get it silently converted into a clipboard snippet instead of a saved file. That's a real (if specific) use case we'd be breaking and it's a better argument than any size cap. So rather than bound the ephemeral path, I've removed it. Details below.

  1. Share sheet text/link sharing is unchanged, here and in the open iOS PR.
  2. <md5>.txt and <md5>.url save to the Taildrop folder like any other file. This does block text/link sharing until a folder is picked, but that's consistent with every other share and it deletes a lot of code.
  3. .url files are deleted on either "open link" or dismiss. Android has no handler for the format and I legitimately can't think of a reason to keep one lingering on your phone.
  4. .txt files are never deleted. Copy is offered when the file is small enough to be worth reading back; otherwise it's just a file. Anyone already sending themselves <md5>.txt log dumps is unaffected.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm this is probably even more niche but if we get a .url file and the user dismisses the banner then the file gets deleted right? probably obscure enough that it is very unlikely to happen but just wanted to clarify

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in the latest revision, that dismiss action removes the .url file & users would need to reshare it. On mobile, this seems like a reasonable trade off to avoid a pile of unusable .url files.

iOS actually does support .webloc, but even there, it seems awkward to persist those files. I updated its PR to behave the same as Android does here.

Comment thread android/src/main/java/com/tailscale/ipn/InlineShareActionReceiver.kt Outdated
Comment thread android/src/main/java/com/tailscale/ipn/util/ShareFileHelper.kt Outdated
This enhances the Android share feature as follows:
- Text and URLs will be recognized and shared via the .txt and .url files
- Files, text, and URLs will now show a small banner atop the devices list, mirroring iOS behavior.
- If multiple taildrops arrive, tapping the banner will open a bottom sheet to take action on them individually.
- Files can be opened or the enclosing taildrop folder opened.
- Text can be copied.
- URLs can be opened in the default browser.
- "Recently Used" taildrop targets will be locally cached and presented atop the list. Presentation & max length matches iOS behavior.
- Simple polling for online status is added to avoid the need to reload the share sheet to find devices that connect while it is visible.
- Fixes a bug that can cause the share sheet to be non-functional if you share; don't exit the view; share again

The iOS and macOS share extensions are currently receiving minor
Taildrop enhancements to support sharing of Text and URLs from
the OS-level share menu. To allow older clients to seamlessly receive
shares from newer clients, the .txt and .url suffixes were chosen.

iOS & macOS (in tailscale/corp#44001), Android
(with these changes), and soon Windows and Linux will recognize these
file types and respond accordingly upon receipt of them:
- .txt: Notification presented to "copy" the text to the clipboard.
- .url: Notification presented to "open" the link

For platforms that lack in-app notifications, the files will additionally
be saved to the Taildrop folder as "Text [timestamp].txt",
"URL [timestamp].inetloc", or "URL [timestamp].url". This addresses
a limitation for users who explicitly opt-out of app notifications. Those
users would otherwise receive no transferred content whatsoever.

Upon use or dismissal of URL shares, the .url file will be deleted. This
is done to avoid piling up generally unusable .url files on Android in the
Taildrop folder.

All other taildrop file transfer functionality remain unchanged.

updates tailscale/tailscale#4896
updates tailscale/tailscale#4996
fixes tailscale/tailscale#16850

Signed-off-by: Will Hannah <willh@tailscale.com>
@willh-ts
willh-ts force-pushed the will/taildrop/links branch from 31569a6 to 53a231e Compare August 28, 2026 18:49
@willh-ts
willh-ts requested a review from kari-ts August 28, 2026 18:50
@willh-ts
willh-ts merged commit b9d01ca into main Sep 2, 2026
5 checks passed
@willh-ts
willh-ts deleted the will/taildrop/links branch September 2, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Taildrop sharing should share text if stream is unavailable

2 participants